#include "helper.h"
#include "monitor.h"
#include "reg.h"

extern uint32_t instr;
extern char assembly[80];

/* decode R-type instrucion */
static void decode_r_type(uint32_t instr) {
	//rs
	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);
	//rt
	op_src2->type = OP_TYPE_REG;
	op_src2->imm = (instr & RT_MASK) >> (RD_SIZE + SHAMT_SIZE + FUNC_SIZE);
	op_src2->val = reg_w(op_src2->reg);
	//rd
	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RD_MASK) >> (SHAMT_SIZE + FUNC_SIZE);
}

make_helper(and){
	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val & op_src2->val);
	sprintf(assembly, "and   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(nor){
	decode_r_type(instr);
	reg_w(op_dest->reg) = (~(op_src1->val | op_src2->val));
	sprintf(assembly, "nor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(or){
	decode_r_type(instr);
	reg_w(op_dest->reg) = ((op_src1->val | op_src2->val));
	sprintf(assembly, "or   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(xor){
	decode_r_type(instr);
	reg_w(op_dest->reg) = ((op_src1->val ^ op_src2->val));
	sprintf(assembly, "xor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(add){
	decode_r_type(instr);
	if(IntegerOverflow(op_src1->val , op_src2 -> val)){
		sprintf(assembly, "add   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
		cp0_w(R_Cause) = (cp0_w(R_Cause) & CP0_CAUSE_EXECCODE_CLEAR_MASK) | CP0_CAUSE_EXECCODE_Ov_MASK;
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_Status) = cp0_w(R_Status) | CP0_STATUS_EXL_MASK;
		cpu.pc = EXCEPTION_HANDLER_ADDR;
		sprintf(assembly, "add: IntegerOverflow");
		return;
	}
	reg_w(op_dest->reg) = (op_src1->val + op_src2->val);
	sprintf(assembly, "add   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(addu){
	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val + op_src2->val);
	sprintf(assembly, "addu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(sub){
	decode_r_type(instr);
	if(IntegerOverflow(op_src1->val , ~(op_src2->val) + 1)){
		sprintf(assembly, "sub   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
		cp0_w(R_Cause) = (cp0_w(R_Cause) & CP0_CAUSE_EXECCODE_CLEAR_MASK) | CP0_CAUSE_EXECCODE_Ov_MASK;
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_Status) = cp0_w(R_Status) | CP0_STATUS_EXL_MASK;
		cpu.pc = EXCEPTION_HANDLER_ADDR;
		sprintf(assembly, "sub: IntegerOverflow");
		return;
	}
	reg_w(op_dest->reg) = (op_src1 ->val - op_src2->val);
	sprintf(assembly, "sub   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(subu){
	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1 ->val - op_src2->val);
	sprintf(assembly, "subu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(slt){
	decode_r_type(instr);
	if(((int)(op_src1->val)) < ((int)(op_src2->val))){
		reg_w(op_dest->reg) = 0x00000001;
	}
	else{
		reg_w(op_dest->reg) = 0x00000000;
	}
	sprintf(assembly, "slt   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(sltu){
	decode_r_type(instr);
	if(((uint32_t)(op_src1->val)) < ((uint32_t)(op_src2->val))){
		reg_w(op_dest->reg) = 0x00000001;
	}
	else{
		reg_w(op_dest->reg) = 0x00000000;
	}
	sprintf(assembly, "sltu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(sllv){
	decode_r_type(instr);
	int offset = (op_src1->val)& 0x0000001f; 
	reg_w(op_dest->reg) = (op_src2->val) << offset; 
	sprintf(assembly, "sllv   %s,   %s,   %d", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg),offset);
}

make_helper(sll){
	decode_r_type(instr);
	if(instr == 0){
		sprintf(assembly, "nop");
		return;
	}
	reg_w(op_dest->reg) = (op_src2->val) << ((instr&SHAMT_MASK) >> FUNC_SIZE);
	sprintf(assembly, "sll   %s,   %s,   %d", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg),((instr&SHAMT_MASK) >> FUNC_SIZE));
}

make_helper(div){
	decode_r_type(instr);
	if(op_src2->val == 0){
		sprintf(assembly, "div   %s,   %s   ASSERT : div 0 " , REG_NAME(op_src1->reg),REG_NAME(op_src2->reg) );
		Assert(false, "ASSERT : div 0");
	}
	cpu.lo = ((int)(op_src1->val))/((int)(op_src2->val));
	cpu.hi = ((int)(op_src1->val))%((int)(op_src2->val));
	sprintf(assembly, "div   %s,   %s" , REG_NAME(op_src1->reg),REG_NAME(op_src2->reg) );
}

make_helper(divu){
	decode_r_type(instr);
	if(op_src2->val == 0){
		sprintf(assembly, "divu   %s,   %s   ASSERT : div 0 " , REG_NAME(op_src1->reg),REG_NAME(op_src2->reg) );
		Assert(false, "ASSERT : div 0");
	}
	cpu.lo = ((uint32_t)(op_src1->val))/((uint32_t)(op_src2->val));
	cpu.hi = ((uint32_t)(op_src1->val))%((uint32_t)(op_src2->val));
	sprintf(assembly, "divu   %s,   %s" , REG_NAME(op_src1->reg),REG_NAME(op_src2->reg) );
}

make_helper(mult){
	decode_r_type(instr);
	uint64_t result = ((int)(op_src1->val)) * ((int)(op_src2->val));
	cpu.lo = result & 0x00000000ffffffff;
	cpu.hi = result & 0xffffffff00000000;
	sprintf(assembly, "mult   %s,   %s" , REG_NAME(op_src1->reg),REG_NAME(op_src2->reg) );
}
make_helper(multu){
	decode_r_type(instr);
	uint64_t result = ((uint32_t)(op_src1->val)) * ((uint32_t)(op_src2->val));
	cpu.lo = result & 0x00000000ffffffff;
	cpu.hi = result & 0xffffffff00000000;
	sprintf(assembly, "multu   %s,   %s" , REG_NAME(op_src1->reg),REG_NAME(op_src2->reg) );
}

make_helper(srav){
	decode_r_type(instr);
	int offset = (op_src1->val)& 0x0000001f;
	reg_w(op_dest->reg) = ((int)(op_src2->val)) >> offset; 
	sprintf(assembly, "srav   %s,   %s,   %d", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg),offset);
}
make_helper(sra){
	decode_r_type(instr);
	int offset = (instr & SHAMT_MASK) >> FUNC_SIZE;
	reg_w(op_dest->reg) = ((int)(op_src2->val)) >> offset; 
	sprintf(assembly, "sra   %s,   %s,   %d", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg),offset);
}
make_helper(srlv){
	decode_r_type(instr);
	int offset = (op_src1->val)& 0x0000001f;
	reg_w(op_dest->reg) = ((uint32_t)(op_src2->val)) >> offset; 
	sprintf(assembly, "srlv   %s,   %s,   %d", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg),offset);
	
}
make_helper(srl){
	decode_r_type(instr);
	int offset = (instr & SHAMT_MASK) >> FUNC_SIZE;
	reg_w(op_dest->reg) = ((uint32_t)(op_src2->val)) >> offset; 
	sprintf(assembly, "sra   %s,   %s,   %d", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg),offset);
}

make_helper(jr){
	decode_r_type(instr);
	cpu.pc = op_src1->val;
	sprintf(assembly, "jr   %s", REG_NAME(op_src1->reg));
	cpu.pc -= 4;
}

make_helper(jalr){
	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.pc + 8;
	cpu.pc = op_src1->val;
	sprintf(assembly, "jalr   %s", REG_NAME(op_src1->reg));
	cpu.pc -= 4;
}

make_helper(beq){
	decode_r_type(instr);
	if((op_src1->val) == (op_src2->val)){
		cpu.pc = (uint32_t)((int)(cpu.pc) + (int)sign_extend_16((instr & IMM_MASK) << 2));
		sprintf(assembly, "beq   GO pc = 0x%08x ", cpu.pc);
		return;
	}
	sprintf(assembly, "beq   0x%08x NO", (uint32_t)((int)(cpu.pc) + (int)sign_extend_16((instr & IMM_MASK) << 2)));
}
make_helper(bne){
	decode_r_type(instr);
	if((op_src1->val) != (op_src2->val)){
		cpu.pc = (uint32_t)((int)(cpu.pc) + (int)sign_extend_16((instr & IMM_MASK) << 2));
		sprintf(assembly, "bne   GO pc = %d ", cpu.pc);
		return;
	}
	sprintf(assembly, "bne   0x%08x NO", (uint32_t)((int)(cpu.pc) + (int)sign_extend_16((instr & IMM_MASK) << 2)));
}

make_helper(bgez){
	decode_r_type(instr);
	if(op_src1->val >= 0){
		cpu.pc += sign_extend_16((instr & IMM_MASK) << 2);
		sprintf(assembly, "bgez   %d GO pc = %d ", sign_extend_16((instr & IMM_MASK) << 2),cpu.pc);
		return;
	}
	sprintf(assembly, "bgez   %d NO", sign_extend_16((instr & IMM_MASK) << 2));
}

make_helper(bgtz){
	decode_r_type(instr);
	if(op_src1->val > 0){
		cpu.pc += sign_extend_16((instr & IMM_MASK) << 2);
		sprintf(assembly, "bgtz   %d GO pc = %d ", sign_extend_16((instr & IMM_MASK) << 2),cpu.pc);
		return;
	}
	sprintf(assembly, "bgtz   %d NO", sign_extend_16((instr & IMM_MASK) << 2));
}

make_helper(blez){
	decode_r_type(instr);
	if(op_src1->val <= 0){
		cpu.pc += sign_extend_16((instr & IMM_MASK) << 2);
		sprintf(assembly, "blez   %d GO pc = %d ", sign_extend_16((instr & IMM_MASK) << 2),cpu.pc);
		return;
	}
	sprintf(assembly, "blez   %d NO", sign_extend_16((instr & IMM_MASK) << 2));
}

make_helper(bltz){
	decode_r_type(instr);
	if(op_src1->val < 0){
		cpu.pc += sign_extend_16((instr & IMM_MASK) << 2);
		sprintf(assembly, "bltz   %d GO pc = %d ", sign_extend_16((instr & IMM_MASK) << 2),cpu.pc);
		return;
	}
	sprintf(assembly, "bltz   %d NO", sign_extend_16((instr & IMM_MASK) << 2));
}

make_helper(bgezal){
	decode_r_type(instr);
	reg_w(31) = cpu.pc + 8;
	if(op_src1->val >= 0){
		cpu.pc += sign_extend_16((instr & IMM_MASK) << 2);
		sprintf(assembly, "bgezal   %d GO pc = %d ", sign_extend_16((instr & IMM_MASK) << 2),cpu.pc);
		return;
	}
	sprintf(assembly, "bgezal   %d NO", sign_extend_16((instr & IMM_MASK) << 2));
}

make_helper(bltzal){
	decode_r_type(instr);
	reg_w(31) = cpu.pc + 8;
	if(op_src1->val < 0){
		cpu.pc += sign_extend_16((instr & IMM_MASK) << 2);
		sprintf(assembly, "bltzal   %d GO pc = %d ", sign_extend_16((instr & IMM_MASK) << 2),cpu.pc);
		return;
	}
	sprintf(assembly, "bltzal   %d NO", sign_extend_16((instr & IMM_MASK) << 2));
}

make_helper(mfhi){
	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.hi;
	sprintf(assembly, "mfhi");
}
make_helper(mflo){
	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.lo;
	sprintf(assembly, "mflo");
}
make_helper(mthi){
	decode_r_type(instr);
	cpu.hi = reg_w(op_dest->reg);
	sprintf(assembly, "mthi");
}
make_helper(mtlo){
	decode_r_type(instr);
	cpu.lo = reg_w(op_dest->reg);
	sprintf(assembly, "mtlo");
}

make_helper(PC0){
	if(instr&0x02000000){
		cp0.status = (cp0.status & CP0_STATUS_EXL_CLEAR_MASK) | CP0_STATUS_EXL_MASK;
		cpu.pc = cp0_w(R_EPC) + 4;
		sprintf(assembly, "eret");
		return;
	}
	decode_r_type(instr);
	if(op_src1->reg == 0 ){
		reg_w(op_src2->reg) = cp0_w(op_dest->reg);
		sprintf(assembly, "mfc0");
		return;
	}
	if(op_src1->reg == 4){
		cp0_w(op_dest->reg) = reg_w(op_src2->reg);
		sprintf(assembly, "mtc0");
		return;
	}
	Assert(false, "PC0: uncognized asm \n");
}